/*
* Terrain.hpp
*
*  Created on: 14/gen/2013
*      Author: stefano.pesenti
*/

#ifndef __ZEN_TERRAIN_HPP__
#define __ZEN_TERRAIN_HPP__

namespace zen {
	namespace video {

		class Section {
		private:
			float* sectionValues;
			float** section;

			void free(float* values, float** section);
			void refreshSection();

		public:
			static int dimension;
			Section() :
				sectionValues(0), section(0) {
			}

			Section(const Section& other) :
				sectionValues(0), section(0) {
					*this = other;
			}

			~Section() {
				free(this->sectionValues, this->section);
			}

			Section &operator=(const Section &other) {
				if (this->sectionValues != 0) {
					float* tempValues = this->sectionValues;
					float** temp = this->section;
					this->sectionValues = 0;
					this->section = 0;

					try {
						this->sectionValues = new float[Section::dimension * Section::dimension];
						//this->section = new float*[Section::dimension];
						refreshSection();
						free(tempValues, temp);
					} catch (std::bad_alloc &e) {
						delete this->sectionValues;
						this->sectionValues = tempValues;
						this->section = temp;
						throw e;
					}
				} else {
					this->sectionValues = new float[Section::dimension * Section::dimension];
					try {
						//this->section = new float*[Section::dimension];
						refreshSection();
					} catch (std::bad_alloc &e) {
						delete this->sectionValues;
						throw e;
					}
				}

				int valueDimension = Section::dimension * Section::dimension;
				for (int i = 0; i < valueDimension; i++) {
					this->sectionValues[i] = other.sectionValues[i];
					//if (i < Section::dimension)
					//	this->section[i] = &other.sectionValues[i * Section::dimension];
				}

				return *this;
			}

			static void setDimension(int dim) {
				if (Section::dimension <= 0)
					Section::dimension = dim;
			}

			bool initialize(float* sectionValues);
			float** getSectionValues() const;
		};

		void Section::refreshSection() {
			delete[] section;
			section = new float*[Section::dimension];
			for (int i = 0; i < Section::dimension; i++)
				this->section[i] = &sectionValues[i * Section::dimension];
		}

		void Section::free(float* values, float** section) {
			delete[] values;
			delete[] section;
		}

		bool Section::initialize(float* sectionValues) {
			if (sectionValues == 0)
				return false;

			this->sectionValues = new float[dimension * dimension];
			try {
				this->section = new float*[dimension];
			} catch (std::bad_alloc& e) {
				delete this->sectionValues;

				throw e;
			}

			int valueDimension = Section::dimension * Section::dimension;
			for (int i = 0; i < valueDimension; i++) {
				this->sectionValues[i] = sectionValues[i];
				if (i < Section::dimension)
					this->section[i] = &sectionValues[i * Section::dimension];
			}

			return true;
		}

		float** Section::getSectionValues() const {
			return this->section;
		}

		//-----------------------------------------
		class Terrain {
		private:
			TextureManager* textureManager;

			Section* mapValues;
			Section** map;

			PTexture grassTexture;
			int cellPerTexture;

			int mapWidth;
			int mapHeight;

			float getUValue(int sectionX, int cellX);
			float getVValue(int sectionY, int cellY);

			void drawSection(int x, int y);
			void linkCellXAxis(int x1, int y1, int x2, int y2);
			void linkCellYAxis(int x1, int y1, int x2, int y2);
			void linkCellDiagonal(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);
		public:
			Terrain() :
				mapValues(0), map(0), cellPerTexture(-1), mapWidth(-1), mapHeight(-1) {
			}

			~Terrain() {
				delete this->mapValues;
				delete[] this->map;
			}

			bool initialize(int mapWidth, int mapHeight, std::string texturePath, int cellPerTexture);
			void setGrassTexture(std::string path);
			bool loadSection(int x, int y, const Section &section);
			bool removeSection(int x, int y);
			bool isSectionLoaded(int x, int y);

			void draw(int xPotision, int yPosition, int visibleRange);
		};

		float Terrain::getUValue(int sectionX, int cellX) {
			return (float) ((sectionX * Section::dimension + cellX) % this->cellPerTexture) / this->cellPerTexture; 
		}

		float Terrain::getVValue(int sectionY, int cellY) {
			return (float) ((sectionY * Section::dimension + cellY) % this->cellPerTexture) / this->cellPerTexture; 
		}

		void Terrain::drawSection(int x, int y) {
			glm::mat4 transform = glm::translate((float) x * Section::dimension, (float) y * Section::dimension, 0.0f);
			glm::scale(transform, 0.5f, 0.5f, 0.5f);
			glPushMatrix();
			glLoadMatrixf(&transform[0][0]);
			float const* const* sectionValues = this->map[x][y].getSectionValues();
			glBegin (GL_QUADS);
			int end = Section::dimension - 1;
			for (int i = 0; i < end; i++) {
				for (int j = 0; j < end; j++) {
					//first cell point
					float u = getUValue(x, i);
					float nextU = getUValue(x, i + 1);
					float v = getVValue(y, j);
					float nextV = getVValue(y, j + 1);
					if(nextU == 0.0)
						nextU = 1.0;
					if(nextV == 0.0)
						nextV = 1.0;

					//first cell point
					glTexCoord2f(u, v);
					glVertex3f((float)i, (float)j, sectionValues[i][j]);

					//second cell point
					glTexCoord2f(u, nextV);
					glVertex3f((float)i, (float)j + 1, sectionValues[i][j + 1]);

					//third cell point
					glTexCoord2f(nextU, nextV);
					glVertex3f((float)i + 1, (float)j + 1, sectionValues[i + 1][j + 1]);

					//fourth cell point
					glTexCoord2f(nextU, v);
					glVertex3f((float)i + 1, (float)j, sectionValues[i + 1][j]);
				}
			}
			glEnd();
			glPopMatrix();
		}

		void Terrain::linkCellXAxis(int x1, int y1, int x2, int y2) {
			glm::mat4 transform = glm::translate((float) x1 * Section::dimension, (float) y1 * Section::dimension, 0.0f);
			glm::scale(transform, 0.5f, 0.5f, 0.5f);
			glPushMatrix();
			glLoadMatrixf(&transform[0][0]);
			float const* const* firstSectionValues = this->map[x1][y1].getSectionValues();
			float const* const* secondSectionValues = this->map[x2][y2].getSectionValues();
			int end = Section::dimension - 1;
			glBegin (GL_QUADS);
			for (int y = 0; y < end; y++) {
				float u = getUValue(x1, Section::dimension - 1);
				float v = getVValue(y1, y);
				float nextU = getUValue(x1, Section::dimension);
				float nextV = getVValue(y1, y + 1);

				if(nextU == 0.0)
					nextU = 1.0;
				if(nextV == 0.0)
					nextV = 1.0;

				glTexCoord2f(u, v);
				glVertex3f((float)end, (float)y, firstSectionValues[end][y]);
				glTexCoord2f(u, nextV);
				glVertex3f((float)end, (float)y + 1, firstSectionValues[end][y + 1]);
				glTexCoord2f(nextU, nextV);
				glVertex3f((float)end + 1, (float)y + 1, secondSectionValues[0][y + 1]);
				glTexCoord2f(nextU, v);
				glVertex3f((float)end + 1, (float)y, secondSectionValues[0][y]);
			}
			glEnd();
			glPopMatrix();
		}

		void Terrain::linkCellYAxis(int x1, int y1, int x2, int y2) {
			glm::mat4 transform = glm::translate((float) x1 * Section::dimension, (float) y1 * Section::dimension, 0.0f);
			glm::scale(transform, 0.5f, 0.5f, 0.5f);
			glPushMatrix();
			glLoadMatrixf(&transform[0][0]);
			float const* const* firstSectionValues = this->map[x1][y1].getSectionValues();
			float const* const* secondSectionValues = this->map[x2][y2].getSectionValues();
			int end = Section::dimension - 1;
			glBegin (GL_QUADS);
			for (int x = 0; x < end; x++) {
				float u = getUValue(x1, x);
				float v = getVValue(y1, Section::dimension - 1);
				float nextU = getUValue(x1, x + 1);
				float nextV = getVValue(y1, Section::dimension);
				if(nextU == 0.0)
					nextU = 1.0;
				if(nextV == 0.0)
					nextV = 1.0;
				glTexCoord2f(u, v);
				glVertex3f((float)x, (float)end, firstSectionValues[x][end]);
				glTexCoord2f(u, nextV);
				glVertex3f((float)x, (float)end + 1, secondSectionValues[x][0]);
				glTexCoord2f(nextU, nextV);
				glVertex3f((float)x + 1, (float)end + 1, secondSectionValues[x + 1][0]);
				glTexCoord2f(nextU, v);
				glVertex3f((float)x + 1, (float)end, firstSectionValues[x + 1][end]);
			}
			glEnd();
			glPopMatrix();
		}

		void Terrain::linkCellDiagonal(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
			glm::mat4 transform = glm::translate((float) x1 * Section::dimension, (float) y1 * Section::dimension, 0.0f);
			glm::scale(transform, 0.5f, 0.5f, 0.5f);
			glPushMatrix();
			glLoadMatrixf(&transform[0][0]);
			float const* const* firstSectionValues = this->map[x1][y1].getSectionValues();
			float const* const* secondSectionValues = this->map[x2][y2].getSectionValues();
			float const* const* thirdSectionValues = this->map[x3][y3].getSectionValues();
			float const* const* fourthSectionValues = this->map[x4][y4].getSectionValues();
			int end = Section::dimension - 1;
			glBegin (GL_QUADS);
			float u = getUValue(x1, Section::dimension - 1);
			float v = getVValue(y1, Section::dimension - 1);
			float nextU = getUValue(x1, Section::dimension);
			float nextV = getVValue(y1, Section::dimension);
			if(nextU == 0.0)
				nextU = 1.0;
			if(nextV == 0.0)
				nextV = 1.0;
			glTexCoord2f(u, v);
			glVertex3f((float)end, (float)end, firstSectionValues[end][end]);
			glTexCoord2f(u, nextV);
			glVertex3f((float)end, (float)end + 1, secondSectionValues[end][0]);
			glTexCoord2f(nextU, nextV);
			glVertex3f((float)end + 1, (float)end + 1, thirdSectionValues[0][0]);
			glTexCoord2f(nextU, v);
			glVertex3f((float)end + 1, (float)end, fourthSectionValues[0][end]);
			glEnd();
			glPopMatrix();
		}

		bool Terrain::initialize(int mapWidth, int mapHeight, std::string texturePath, int cellPerTexture) {
			if (mapWidth <= 0 || mapHeight <= 0 || texturePath.empty() || cellPerTexture <= 0)
				return false;

			this->grassTexture = textureManager->loadFile(texturePath);
			this->cellPerTexture = cellPerTexture;
			this->mapWidth = mapWidth;
			this->mapHeight = mapHeight;
			this->mapValues = new Section[this->mapWidth * this->mapHeight];
			this->map = new Section*[this->mapWidth];

			for (int i = 0; i < this->mapWidth; i++) {
				this->map[i] = &this->mapValues[i * this->mapWidth];
			}

			return true;
		}

		bool Terrain::loadSection(int x, int y, const Section &section) {
			if (x < 0 || x >= this->mapWidth || y < 0 || y >= this->mapHeight)
				return false;

			this->map[x][y] = section;
			return true;
		}

		bool Terrain::removeSection(int x, int y) {
			if (x < 0 || x >= this->mapWidth || y < 0 || y >= this->mapHeight)
				return false;

			return true;
		}

		bool Terrain::isSectionLoaded(int x, int y) {
			if (x < 0 || x >= this->mapWidth || y < 0 || y >= this->mapHeight)
				return false;

			return this->map[x][y].getSectionValues() != 0;
		}

		void Terrain::draw(int xPotision, int yPosition, int visibleRange) {
			this->grassTexture->activate();
			float u = 0, v = 0;
			for (int x = xPotision - visibleRange; x <= xPotision + visibleRange; x++) {
				for (int y = yPosition - visibleRange; y <= yPosition + visibleRange; y++) {
					if (x < 0 || y < 0 || x >= this->mapWidth || y >= this->mapHeight || this->map[x][y].getSectionValues() == 0)
						continue;

					bool xInDrawingBound = x < this->mapWidth - 1 && this->map[x + 1][y].getSectionValues() != 0;
					bool yInDrawingBound = y < this->mapHeight - 1 && this->map[x][y + 1].getSectionValues() != 0;
					drawSection(x, y);
					if (xInDrawingBound) {
						linkCellXAxis(x, y, x + 1, y);
					}
					if (yInDrawingBound) {
						linkCellYAxis(x, y, x, y + 1);
					}
					if (xInDrawingBound && yInDrawingBound && this->map[x + 1][y + 1].getSectionValues() != 0) {
						linkCellDiagonal(x, y, x, y + 1, x + 1, y + 1, x + 1, y);
					}
				}
			}
		}

		int Section::dimension;

	} // namespace video
} // namespace zen

#endif /* TERRAIN_HPP_ */
